#include <stdio.h>
#include "raylib.h"
#include "types.h"
#define DYN_STR_IMPLEMENTATION
#include "dynamic_string.h"

#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
#define TARGET_FPS 60
#define MARGIN 10
#define PREVIEW_LINES 10
#define FONT_SIZE 20

const char *pcwd;

typedef struct picker_window {
    i32 x;
    i32 y;
    i32 width;
    i32 height;
    i32 margin;
    u32 font_size;
    Color list_color;
} picker_window;

picker_window pwin = {
    .x = MARGIN,
    .y = MARGIN,
    .width = SCREEN_WIDTH - 2*MARGIN,
    .height = 160,
    .margin = MARGIN,
    .font_size = FONT_SIZE,
    .list_color = DARKGRAY,
};

typedef struct preview_csv_window {
    i32 x;
    i32 y;
    i32 width;
    i32 height;
    i32 margin;
    u32 font_size;
    Color grid_bg;
    Color grid_lines;
} preview_csv_window;

preview_csv_window pcsv = {
    .x = MARGIN,
    .y = MARGIN,
    .width = SCREEN_WIDTH - 2*MARGIN,
    .height = SCREEN_HEIGHT,
    .margin = MARGIN,
    .font_size = FONT_SIZE,
    .grid_bg = LIGHTGRAY,
    .grid_lines = DARKGRAY
};

typedef struct String_Builder_Array CSV_Paths;

typedef struct String_Builder_Array Raw_CSV_Row;
typedef struct String_Builder_Array Raw_CSV_Col;

typedef struct String_Builder_Array_2D Raw_CSV;

String_Builder cwd = {0};
String_Builder csv_path = {0};
String_Builder csv_raw = {0};
String_View cwd_view = {0};
String_View csv_path_view = {0};
String_View csv_raw_rows = {0};
String_View csv_raw_columns = {0};
CSV_Paths csv_paths = {0};
Raw_CSV raw_csv = {0};
Raw_CSV raw_csv_col = {0};
String_Builder header_count = {0};

typedef enum active_window_t {
    AW_PICKER = 0,
    AW_SHOW,
    AW_LEN,
} active_window_t;

active_window_t active_window = AW_PICKER;

Vector2 draw_raw_csv(Vector2 bottom_of_button) {
    raw_csv_col.length = 0;
    u64 num_cols = raw_csv.items->length;
    Raw_CSV_Col cols[num_cols];
    for(u64 col=0; col<num_cols; ++col) {
        cols[col].items = NULL;
        cols[col].length = 0;
        cols[col].capacity = 0;
    }
    for(u64 row=0; row<raw_csv.length; ++row) {
        Raw_CSV_Row line = raw_csv.items[row];
        for(u64 col=0; col<line.length; ++col) {
            const char *text = line.items[col].items;
            sba_append_cstr(&cols[col], text);
        }
    }
    for(u64 col=0; col<num_cols; ++col) {
        sba2d_append(&raw_csv_col, &cols[col]);
        sba_free(cols[col]);
    }
    
    Vector2 text_size = MeasureTextEx(
        GetFontDefault(),
        raw_csv_col.items->items->items,
        pcsv.font_size, pcsv.font_size/10.0
    );
        
    pcsv.width = GetScreenWidth()-2*pcsv.margin;
    pcsv.height = GetScreenHeight()-pcsv.margin;
    i32 y_offset = pcsv.y + bottom_of_button.y;
    Rectangle rec = {
        .x = pcsv.x,
        .y = y_offset,
        .width = pcsv.width,
        .height = pcsv.height - y_offset - (PREVIEW_LINES-1)*(text_size.y+pcsv.margin),
    };
    DrawRectangle(
        rec.x,
        rec.y,
        rec.width,
        rec.height,
        pcsv.grid_bg
    );
    i32 col_offset = rec.x + pcsv.margin;
    for(u64 col=0; col<num_cols; ++col) {
        i32 row_offset = rec.y + pcsv.margin*0.5;
        Raw_CSV_Col column = raw_csv_col.items[col];
        i32 max_text_size = 0;
        Vector2 text_size = {0};
        for(u64 row=0; row<column.length; ++row) {
            const char *text = column.items[row].items;
            text_size = MeasureTextEx(GetFontDefault(), text, pcsv.font_size, pcsv.font_size/10.0);
            DrawText(
                text,
                col_offset,
                row_offset,
                pcsv.font_size,
                DARKGRAY
            );
            max_text_size = (max_text_size < text_size.x) ? text_size.x : max_text_size;
            row_offset += text_size.y;
        }
        col_offset += pcsv.margin + max_text_size;
    }
    Vector2 ret_corner = {0};
    ret_corner.x = rec.x+rec.width;
    ret_corner.y = rec.y+rec.height;
    return ret_corner;
}

Vector2 draw_header_input(Vector2 bottom) {
    String_Builder *input = &header_count;
    i32 y_offset = pcsv.y + bottom.y;
    const char * tooltip = "Header Rows:";
    const char * max_count = "99";
    Vector2 tt_length = MeasureTextEx(
        GetFontDefault(),
        tooltip,
        pcsv.font_size, pcsv.font_size/10.0
    );
    Vector2 input_length = MeasureTextEx(
        GetFontDefault(),
        max_count,
        pcsv.font_size, pcsv.font_size/10.0
    );
    Rectangle outer_box = {
        .x = pcsv.margin,
        .y = y_offset,
        .width = tt_length.x + input_length.x + 3.5*pcsv.margin,
        .height = tt_length.y + pcsv.margin,
    };
    DrawRectangleRec(outer_box, pcsv.grid_bg);
    bool over_outer_box = CheckCollisionPointRec(GetMousePosition(), outer_box);
    if(over_outer_box) {
        DrawText(
            tooltip,
            2*pcsv.margin,
            y_offset+0.5*pcsv.margin,
            pcsv.font_size,
            DARKBLUE
        );
    } else {
        DrawText(
            tooltip,
            2*pcsv.margin,
            y_offset+0.5*pcsv.margin,
            pcsv.font_size,
            DARKGRAY
        );
    }
    Rectangle input_box = {
        .x = tt_length.x + 2.5*pcsv.margin,
        .y = y_offset+0.5*pcsv.margin,
        .width = input_length.x + pcsv.margin,
        .height = input_length.y,
    };
    DrawRectangleRec(input_box, DARKGRAY);

    if(over_outer_box) {
        bool ibeam = CheckCollisionPointRec(GetMousePosition(), input_box);
        if (ibeam) SetMouseCursor(MOUSE_CURSOR_IBEAM);
        else SetMouseCursor(MOUSE_CURSOR_DEFAULT);
        // Get char pressed (unicode character) on the queue
        u8 key = GetCharPressed();
        // Check if more characters have been pressed on the same frame
        while (key > 0) {
            // NOTE: Only allow digits
            // two digits and null term
            if ((key >= '0') && (key <= '9') && input->length < 2) {
                sb_append(input, &key, 1);
            }

            key = GetCharPressed();  // Check next character in the queue
        }

        if (IsKeyPressed(KEY_BACKSPACE) || IsKeyPressed(KEY_DELETE)) {
            if (input->length > 0) {
                input->items[input->length-1] = '\0';
                input->length -= 1;
            }
            if (input->length < 0) input->length=0;
        }
    }

    String_Builder printer = {0};
    sb_append(&printer, input->items, input->length);
    sb_append_null(&printer);
    DrawText(printer.items, input_box.x+pcsv.margin*0.5, input_box.y, pcsv.font_size, pcsv.grid_bg);

    Vector2 ret_bottom = {
        .x = input_box.x + input_box.width,
        .y = input_box.y + input_box.height,
    };
    return ret_bottom;
}

void parse_csv_raw(void) {
    csv_raw.length = 0;
    sb_read_entire_file(csv_path_view.data, &csv_raw);
    
    csv_raw_rows.data = csv_raw.items;
    csv_raw_rows.length = csv_raw.length;
    Raw_CSV_Row row = {0};
    String_Builder sb = {0};
    u64 total_bytes = 0;
    for(u64 i=0; i<PREVIEW_LINES; ++i) {
        String_View line = sv_chop_by_delim(&csv_raw_rows, '\n');
        u64 line_bytes = 0;
        u64 length = line.length;
        while(line_bytes < length && length >=1 ) {
            String_View col_sv = sv_chop_by_delim(&line, ',');
            sb_append(&sb, col_sv.data, col_sv.length);
            sb_append_null(&sb);
            sba_append(&row, &sb);
            sb.length = 0;
            line_bytes += col_sv.length;
            // decrement to account for the consumed delimiter 
            length--;
        }
        total_bytes += line_bytes;
        sba2d_append(&raw_csv, &row);
        sba_free(row);
    }
    sb_free(sb);
}

void show_csv(void) {
    DrawText("Selected:", pwin.x, pwin.y, pwin.font_size, LIGHTGRAY);
    pwin.width = GetScreenWidth()-2*pwin.margin;
    DrawRectangle(
        pwin.x, pwin.y+pwin.font_size+pwin.margin, pwin.width, pwin.font_size, LIGHTGRAY
    );
    DrawText(
        csv_path_view.data,
        pwin.x,
        pwin.y+pwin.font_size+pwin.margin,
        pwin.font_size,
        DARKGRAY
    );
    Vector2 bottom = {0};
    { // draw Pick File button
        const char *text = "Pick File";
        Vector2 text_size = MeasureTextEx(GetFontDefault(), text, pcsv.font_size, pcsv.font_size/10.0);
        Rectangle rec = {
            .x = pwin.x,
            .y = pwin.y + 2*(pwin.margin + pwin.font_size),
            .width = text_size.x + 2*pwin.margin,
            .height = text_size.y + pwin.margin,
        };
        bottom.x = rec.x + rec.width;
        bottom.y = rec.y + rec.height;
        Color rect_color = DARKBLUE;
        Color text_color = LIGHTGRAY;
        if (CheckCollisionPointRec(GetMousePosition(), rec)) {
            rect_color = BLUE;
            text_color = BLACK;
            if(IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
                active_window = AW_PICKER;
        }
        DrawRectangle(
            rec.x,
            rec.y,
            rec.width,
            rec.height,
            rect_color
        );
        DrawText(
            text,
            rec.x+pwin.margin,
            rec.y+pwin.margin*0.5,
            pcsv.font_size,
            text_color
        );
    }

    parse_csv_raw();
    bottom = draw_raw_csv(bottom);
    bottom = draw_header_input(bottom);
    sba2d_free(raw_csv);
    // sba2d_free(raw_csv_col);
}

void file_picker(void) {
    FilePathList fpl = {0};
    cwd.length = 0;
    sb_append_cstr(&cwd, GetWorkingDirectory());
    sb_append_null(&cwd);
    cwd_view.data = cwd.items;
    cwd_view.length = cwd.length;
    // Get directory files
    fpl = LoadDirectoryFilesEx(cwd_view.data, ".csv", 0);
    sba_free(csv_paths);
    // Copy paths to memory we control
    for (u32 i=0; i<fpl.count; ++i) {
        sba_append_cstr(&csv_paths, fpl.paths[i]);
    }
    // remove fpl data from raylib arena
    UnloadDirectoryFiles(fpl);
    
    pwin.width = GetScreenWidth()-2*pwin.margin;
    pwin.height = GetScreenHeight()-2*pwin.margin;
    
    DrawRectangle(
        pwin.x, pwin.y, pwin.width, pwin.height, LIGHTGRAY
    );
    u32 offset = pwin.margin;
    Rectangle rec = {0};
    for(u32 i=0; i<csv_paths.length; ++i) {
        const char *path = csv_paths.items[i].items;
        rec.x = pwin.x + 5;
        rec.y = pwin.y + offset;
        rec.width = pwin.width - pwin.margin;
        rec.height = pwin.font_size;
        if (CheckCollisionPointRec(GetMousePosition(), rec)) {
            DrawRectangle(rec.x, rec.y, rec.width, rec.height, pwin.list_color);
            DrawText(
                path,
                rec.x,
                rec.y,
                pwin.font_size,
                LIGHTGRAY
            );
            if(CheckCollisionPointRec(GetMousePosition(), rec) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
                memset(csv_path.items, '\0', csv_path.length);
                csv_path.length = 0;
                sb_append_cstr(&csv_path, path);
                sb_append_null(&csv_path);
                csv_path_view.data = csv_path.items;
                csv_path_view.length = csv_path.length;
                active_window = AW_SHOW;
            }
        } else {
            DrawText(
                path,
                pwin.x + pwin.margin*0.5,
                pwin.y + offset,
                pwin.font_size,
                pwin.list_color
            );
        }
        offset += pwin.font_size;
    }
}

int main(void) {
    SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_ALWAYS_RUN);
    InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "CSV File Reader");
    SetTargetFPS(TARGET_FPS);
 
    while (!WindowShouldClose()) {
        BeginDrawing();
        ClearBackground(DARKGRAY);
        switch (active_window) {
            case AW_PICKER: {
                file_picker();
                break;
            }
            case AW_SHOW: {
                show_csv();
                break;
            }
            default: {
                active_window = AW_PICKER;
                break;
            }
        }
        EndDrawing();
    }
    CloseWindow();

    return 0;
}